/*
Copyright [2020] [https://www.xiaonuo.vip]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：

1.请不要删除和修改根目录下的LICENSE文件。
2.请不要删除和修改Snowy源码头部的版权声明。
3.请保留源码和相关描述文件的项目出处，作者声明等。
4.分发源码时候，请注明软件出处 https://gitee.com/xiaonuobase/snowy
5.在修改包名，模块名称，项目代码等时，请注明软件出处 https://gitee.com/xiaonuobase/snowy
6.若您的项目无法满足以上几点，可申请商业授权，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.sys.modular.coursehomework.service.impl;

import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ZipUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import vip.xiaonuo.core.context.login.LoginContextHolder;
import vip.xiaonuo.core.exception.ServiceException;
import vip.xiaonuo.core.factory.PageFactory;
import vip.xiaonuo.core.pojo.page.PageResult;
import vip.xiaonuo.core.util.PoiUtil;
import vip.xiaonuo.core.util.RarUtil;
import vip.xiaonuo.sys.modular.course.entity.Course;
import vip.xiaonuo.sys.modular.course.service.CourseService;
import vip.xiaonuo.sys.modular.coursehomework.param.HomeworkScoreSummaryParam;
import vip.xiaonuo.sys.modular.coursehomework.result.HomeworkScoreSummaryResult;
import vip.xiaonuo.sys.modular.coursehomework.result.HomeworkScoreSummaryTableData;
import vip.xiaonuo.sys.modular.coursehomework.result.HomeworkScoreSummaryTableHeader;
import vip.xiaonuo.sys.modular.courseschedule.entity.CourseSchedule;
import vip.xiaonuo.sys.modular.courseschedule.service.CourseScheduleService;
import vip.xiaonuo.sys.modular.courseselection.entity.CourseSelection;
import vip.xiaonuo.sys.modular.courseselection.enums.CourseSelectionExceptionEnum;
import vip.xiaonuo.sys.modular.courseselection.service.CourseSelectionService;
import vip.xiaonuo.sys.modular.coursehomework.entity.Homework;
import vip.xiaonuo.sys.modular.coursehomework.enums.HomeworkExceptionEnum;
import vip.xiaonuo.sys.modular.coursehomework.mapper.HomeworkMapper;
import vip.xiaonuo.sys.modular.coursehomework.param.HomeworkParam;
import vip.xiaonuo.sys.modular.coursehomework.param.HomeworkParamForQuery;
import vip.xiaonuo.sys.modular.coursehomework.result.HomeworkResult;
import vip.xiaonuo.sys.modular.coursehomework.service.HomeworkService;
import vip.xiaonuo.sys.modular.file.entity.SysFileInfo;
import vip.xiaonuo.sys.modular.file.result.SysFileInfoResult;
import vip.xiaonuo.sys.modular.file.service.SysFileInfoService;
import vip.xiaonuo.sys.modular.homeworktag.entity.HomeworkTag;
import vip.xiaonuo.sys.modular.project.entity.Project;
import vip.xiaonuo.sys.modular.project.service.ProjectService;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * 作业表service接口实现类
 *
 * @author kongxm
 * @date 2022-07-16 14:40:04
 */
@Service
public class HomeworkServiceImpl extends ServiceImpl<HomeworkMapper, Homework> implements HomeworkService {
    @Autowired
    private SysFileInfoService sysFileInfoService;
    @Autowired
    private CourseSelectionService courseSelectionService;
    @Autowired
    private CourseScheduleService courseScheduleService;
    @Autowired
    private CourseService courseService;
    @Autowired
    private ProjectService projectService;

    @Override
    public PageResult<HomeworkResult> pageForTeacher(HomeworkParamForQuery homeworkParamForQuery) {
        if (LoginContextHolder.me().isSuperAdmin()) {
            log.debug("current user is super admin");
        } else if (LoginContextHolder.me().hasRole("ca")) { //院系领导
            homeworkParamForQuery.setCollegeId(LoginContextHolder.me().getSysLoginUser().getCollId());
        } else if (LoginContextHolder.me().isTeacher()) {
            homeworkParamForQuery.setTeacherId(LoginContextHolder.me().getSysLoginUserId());
        }
        return new PageResult<>(this.baseMapper.pageForTeacher(PageFactory.defaultPage(), homeworkParamForQuery));
    }

    @Override
    public PageResult<HomeworkResult> pageForStudent(HomeworkParamForQuery homeworkParamForQuery) {
        if (!LoginContextHolder.me().isSuperAdmin()) {// if current user isn't admin,filter his own data only
            homeworkParamForQuery.setStudentId(LoginContextHolder.me().getSysLoginUser().getId());
        }
        return new PageResult<>(this.baseMapper.pageForStudent(PageFactory.defaultPage(), homeworkParamForQuery));
    }

    @Override
    public List<Homework> list(HomeworkParam homeworkParam) {
        return this.list();
    }

    @Override
    public void add(HomeworkParam homeworkParam) {
        Homework homework = new Homework();
        BeanUtil.copyProperties(homeworkParam, homework);
        this.save(homework);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(List<HomeworkParam> homeworkParamList) {
        homeworkParamList.forEach(homeworkParam -> {
            this.removeById(homeworkParam.getId());
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(HomeworkParam homeworkParam) {
        Homework homework = this.queryHomework(homeworkParam);
        BeanUtil.copyProperties(homeworkParam, homework);
        this.updateById(homework);
    }

    @Override
    public Homework detail(HomeworkParam homeworkParam) {
        return this.queryHomework(homeworkParam);
    }

    /**
     * 获取作业表
     *
     * @author kongxm
     * @date 2022-07-16 14:40:04
     */
    private Homework queryHomework(HomeworkParam homeworkParam) {
        Homework homework = this.getById(homeworkParam.getId());
        if (ObjectUtil.isNull(homework)) {
            throw new ServiceException(HomeworkExceptionEnum.NOT_EXIST);
        }
        return homework;
    }

    @Override
    public void homeworkExportForTeacher(HomeworkParamForQuery homeworkParamForQuery) {
        List<HomeworkResult> list = this.baseMapper.listForTeacher(homeworkParamForQuery);
        PoiUtil.exportExcelWithStream("作业管理.xls", HomeworkResult.class, list);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long uploadHomework(Long courseSelectionId, MultipartFile file) {
        assert courseSelectionId != null;
        CourseSelection courseSelection = courseSelectionService.getById(courseSelectionId);
        if (ObjectUtil.isNull(courseSelection)) {
            throw new ServiceException(CourseSelectionExceptionEnum.NOT_EXIST);
        }
        Long fileId = sysFileInfoService.uploadFile(file);
        if (fileId != null) {
            Homework persistHomework = getOne(new LambdaQueryWrapper<Homework>()
                    .eq(Homework::getCourseSelectionId, courseSelectionId)
                    .eq(Homework::getCourseScheduleId, courseSelection.getCourseScheduleId()));

            Homework homework = new Homework();
            if (persistHomework == null) {
                homework.setCourseSelectionId(courseSelectionId);
                homework.setCourseScheduleId(courseSelection.getCourseScheduleId());
                homework.setHomeworkFileId(fileId);
                save(homework);
            } else {
                homework.setId(persistHomework.getId());
                homework.setHomeworkFileId(fileId);
                updateById(homework);
            }
        }
        return fileId;
    }

    @Override
    public Long uploadCorrectedHomework(Long id, MultipartFile file) {
        assert id != null;
        Homework persistence = getById(id);
        if (ObjectUtil.isNull(persistence)) {
            throw new ServiceException(HomeworkExceptionEnum.NOT_EXIST);
        }
        Long fileId = sysFileInfoService.uploadFile(file);
        if (fileId != null) {
            Homework homework = new Homework();
            homework.setId(id);
            homework.setCorrectedHomeworkFileId(fileId);
            updateById(homework);
        }
        return fileId;
    }


    @Override
    public HomeworkScoreSummaryTableData queryScoreSummary(HomeworkScoreSummaryParam homeworkScoreSummaryParam) {
        if (homeworkScoreSummaryParam.getSchoolYearId() == null
                || homeworkScoreSummaryParam.getTermId() == null
                || homeworkScoreSummaryParam.getCourseId() == null) {
            return new HomeworkScoreSummaryTableData();
        }
        HomeworkScoreSummaryTableData homeworkScoreSummaryTableData = new HomeworkScoreSummaryTableData();

        List<HomeworkScoreSummaryResult> sourceList = this.baseMapper.queryScoreSummary(homeworkScoreSummaryParam);

        // set table header
        List<HomeworkScoreSummaryTableHeader> tableHeader = new ArrayList<>();
        tableHeader.add(new HomeworkScoreSummaryTableHeader("studentNum", "学号"));
        tableHeader.add(new HomeworkScoreSummaryTableHeader("studentName", "姓名"));
        sourceList.forEach(source -> {
            // 项目分数
            String labelNameProjectScore = String.format("project-score-%s", source.getProjectId());
            if (tableHeader.stream().noneMatch(header -> header.getColumnName().equals(labelNameProjectScore))) {
                tableHeader.add(new HomeworkScoreSummaryTableHeader(labelNameProjectScore, source.getProjectName()));
            }
            // 选课id
            String labelNameCourseSelection = String.format("course-selection-id-%s", source.getProjectId());
            if (tableHeader.stream().noneMatch(header -> header.getColumnName().equals(labelNameCourseSelection))) {
                tableHeader.add(new HomeworkScoreSummaryTableHeader(labelNameCourseSelection, null));
            }
        });
        tableHeader.add(new HomeworkScoreSummaryTableHeader("defendTotalScore", "答辩分数"));
        // 这个是找到的最后日期的答辩分数
        tableHeader.add(new HomeworkScoreSummaryTableHeader("defendTotalScoreLatestCourseSelectionId", null));
        homeworkScoreSummaryTableData.setTableHeaderList(tableHeader);


        //set table data
        List<Map<String, Object>> tableDataList = new ArrayList<>();

        sourceList.forEach(outer -> {
            Long studentId = outer.getStudentId();
            Optional<Map<String, Object>> matched = tableDataList.stream()
                    .filter(inner -> Long.valueOf(inner.get("studentId").toString()).equals(studentId)).findAny();
            if (matched.isPresent()) {
                matched.ifPresent(item -> {
                    String projectScoreKey = String.format("project-score-%s", outer.getProjectId());
                    //如果没有找到相同的项目，写入
                    if (item.get(projectScoreKey) == null) {
                        //如果没有维护项目分数，项目分数用该项目的答辩分数填充
                        setProjectTotalScore(outer, item);
                        return;
                    }
                    //如果在列表中找到了同样的项目id，并且当前项目打分不为空,按 排课轮次递增(sql中已排序)，选择性覆盖
                    if (outer.getProjectTotalScore() != null) {
                        setProjectTotalScore(outer, item);
                    }
                });
            } else {
                LinkedHashMap<String, Object> linkedHashMap = new LinkedHashMap<>();
                linkedHashMap.put("studentId", studentId);
                linkedHashMap.put("studentNum", outer.getStudentNum());
                linkedHashMap.put("studentName", outer.getStudentName());
                //如果没有维护项目分数，项目分数用该项目的答辩分数填充
                Integer projectScore = outer.getProjectTotalScore() == null ? outer.getDefendTotalScore() : outer.getProjectTotalScore();
                linkedHashMap.put(String.format("project-score-%s", outer.getProjectId()), projectScore);
                linkedHashMap.put(String.format("course-selection-id-%s", outer.getProjectId()), outer.getCourseSelectionId());
                tableDataList.add(linkedHashMap);
            }
        });
        tableDataList.forEach(map -> {
            //判断表头中的项目打分是否在数据列表中不存在，如果不存在分数用“-”标识
            tableHeader.stream().filter(header -> header.getColumnName().startsWith("project-score-"))
                    .forEach(header -> {
                        if (!map.containsKey(header.getColumnName())) {
                            map.put(header.getColumnName(), "-");
                        }
                    });
            //如果用户选择课程，但没有打分，默认0分
            map.forEach((key, value) -> {
                if (key.startsWith("project-score-") && value == null) map.put(key, 0);
            });
            Long studentId = Long.valueOf(map.get("studentId").toString());
            //find the last createTime of projects,get the defense score
            sourceList.stream()
                    .filter(source -> source.getStudentId().equals(studentId) && source.getDefendTotalScore() != null)
                    .max(Comparator.comparing(HomeworkScoreSummaryResult::getDefendTotalScoreCreateTime))
                    .ifPresent(source -> {
                        map.put("defendTotalScore", source.getDefendTotalScore());
                        map.put("defendTotalScoreLatestCourseSelectionId", source.getCourseSelectionId());
                    });
        });

        tableDataList.sort(Comparator.comparing(data -> data.get("studentNum").toString()));

        homeworkScoreSummaryTableData.setTableDataList(tableDataList);
        return homeworkScoreSummaryTableData;
    }

    private void setProjectTotalScore(HomeworkScoreSummaryResult outer, Map<String, Object> item) {
        Integer projectScore = outer.getProjectTotalScore() == null ? outer.getDefendTotalScore() : outer.getProjectTotalScore();
        item.put(String.format("project-score-%s", outer.getProjectId()), projectScore);
        item.put(String.format("course-selection-id-%s", outer.getProjectId()), outer.getCourseSelectionId());
    }

    @Override
    public void exportScoreStatistics(HomeworkScoreSummaryParam homeworkScoreSummaryParam) {
        HomeworkScoreSummaryTableData homeworkScoreSummaryTableData = this.queryScoreSummary(homeworkScoreSummaryParam);
        List<ExcelExportEntity> entityList = new ArrayList<>();
        homeworkScoreSummaryTableData.getTableHeaderList().forEach(item -> {
            if (item.getLabelName() == null) return;
            ExcelExportEntity entity = new ExcelExportEntity(item.getLabelName(), item.getColumnName());
            entityList.add(entity);
        });
        PoiUtil.exportExcelWithStream2("分数统计.xls",
                entityList,
                homeworkScoreSummaryTableData.getTableDataList());
    }

    @Override
    public void batchDownloadHomework(List<HomeworkParam> homeworkParams, int type, HttpServletResponse response) {
        if (CollectionUtil.isEmpty(homeworkParams)) return;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
        homeworkParams.forEach(item -> {
            Homework homework = getById(item.getId());
            if (homework == null) return;
            SysFileInfoResult sysFileInfoResult;
            try {
                if (type == 0) {
                    sysFileInfoResult = sysFileInfoService.getFileInfoResult(homework.getHomeworkFileId());
                } else {
                    sysFileInfoResult = sysFileInfoService.getFileInfoResult(homework.getCorrectedHomeworkFileId());
                }
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
            CourseSchedule courseSchedule = courseScheduleService.getById(homework.getCourseScheduleId());
            //开课课程
            if (courseSchedule.getCourseId() == null) return;
            Course course = courseService.getById(courseSchedule.getCourseId());
            if (course == null) return;
            //实验项目
            if (courseSchedule.getProjectId() == null) return;
            Project project = projectService.getById(courseSchedule.getProjectId());
            if (project == null) return;
            try {
                String entryName = course.getName() + File.separator
                        + project.getName() + File.separator
                        + String.format("%s_%s", homework.getId(), sysFileInfoResult.getFileOriginName());
                zipOutputStream.putNextEntry(new ZipEntry(entryName));
                zipOutputStream.write(sysFileInfoResult.getFileBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        IOUtils.closeQuietly(zipOutputStream);
        byte[] bytes = outputStream.toByteArray();
        try {
            response.reset();
            String fileTypeName = type == 0 ? "学生作业" : "批改后作业";
            String fileName = URLEncoder.encode(
                    String.format("%s-%s.zip", fileTypeName, DateFormatUtils.format(new Date(), "yyyyMMddHHmmss")),
                    CharsetUtil.UTF_8);
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
            response.addHeader("Content-Length", "" + bytes.length);
            response.setContentType("application/octet-stream; charset=UTF-8");
            IOUtils.write(bytes, response.getOutputStream());
        } catch (Exception e) {
            throw new ServiceException(HomeworkExceptionEnum.DOWNLOAD_STUDENT_HOMEWORK_ERROR);
        }
    }

    @Override
    public void batchUploadCorrectedHomework(MultipartFile file) throws Exception {
        String contentType = StringUtils.substringAfter(file.getOriginalFilename(), ".");
        log.debug("contentType=" + contentType);

        String unzipFolder = System.getProperty("java.io.tmpdir") + File.separator + System.currentTimeMillis();
        log.debug("unzipFolder=" + unzipFolder);
        FileUtil.mkdir(unzipFolder);
        try {
            if ("rar".equalsIgnoreCase(contentType)) {
                RarUtil.extractRar(file, unzipFolder);
            }
            if ("zip".equalsIgnoreCase(contentType)) {
                ZipUtil.unzip(file.getInputStream(), new File(unzipFolder), Charset.forName("GBK"));
            }
            FileUtil.loopFiles(unzipFolder).forEach(f -> {
                log.debug("f.name" + f.getAbsolutePath());
                String lastStr = StringUtils.substringBefore(f.getName(), "_");
                String newFileName = StringUtils.substringAfter(f.getName(), "_");
                if (StringUtils.isEmpty(lastStr)) return;
                Long homeworkId = Long.valueOf(lastStr);
                File newFile = new File(unzipFolder + File.separator + newFileName);
                boolean isOK = f.renameTo(newFile);
                if (isOK) {
                    Long fileId = sysFileInfoService.uploadFile(newFile);
                    if (fileId != null) {
                        Homework homework = new Homework();
                        homework.setId(homeworkId);
                        homework.setCorrectedHomeworkFileId(fileId);
                        updateById(homework);
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    @Override
    public List<Integer> listFrequencyForTeacher(Long courseId) {
        Long collegeId = null;
        if (!LoginContextHolder.me().isSuperAdmin()) {// if current user isn't admin,filter his college data only
            collegeId = LoginContextHolder.me().getSysLoginUser().getCollId();
        }
        return this.baseMapper.listFrequencyForTeacher(collegeId, courseId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void stuConfirm(Long id) {
        Homework persistHomework = getById(id);
        assert persistHomework != null;

        // update the column stu_confirmed to true
        Homework homework = new Homework();
        homework.setId(id);
        homework.setStuConfirmed(1);
        baseMapper.updateById(homework);

        // according the homework file id, update the file attachment's upload date.
        SysFileInfo sysFileInfo = new SysFileInfo();
        sysFileInfo.setId(persistHomework.getHomeworkFileId());
        sysFileInfo.setCreateTime(new Date());
        sysFileInfo.setUpdateTime(new Date());
        sysFileInfoService.updateById(sysFileInfo);
    }
}
